浏览器插件开发

https://developer.chrome.com/extensions/manifest

2021.4.6 星期二




## chrome extendsion 开发
https://developer.chrome.com/extensions/manifest
360开发文档-书签





manifest.json: name, icons, manifest_version, version, description
browser_action
page_action
options_page/ options_ui
chrome_url_overrides
devtools_page

background
content_scripts
web_accessible_resources

permissions

omnibox


injected-script, event-page,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
{
// 清单文件的版本,这个必须写,而且必须是2
"manifest_version": 2,
// 插件的名称
"name": "demo",
// 插件的版本
"version": "1.0.0",
// 插件描述
"description": "简单的Chrome扩展demo",
// 图标,一般偷懒全部用一个尺寸的也没问题
"icons":
{
"16": "img/icon.png",
"48": "img/icon.png",
"128": "img/icon.png"
},
// 会一直常驻的后台JS或后台页面
"background":
{
// 2种指定方式,如果指定JS,那么会自动生成一个背景页
"page": "background.html"
//"scripts": ["js/background.js"]
},
// 浏览器右上角图标设置,browser_action、page_action、app必须三选一
"browser_action":
{
"default_icon": "img/icon.png",
// 图标悬停时的标题,可选
"default_title": "这是一个示例Chrome插件",
"default_popup": "popup.html"
},
// 当某些特定页面打开才显示的图标
/*"page_action":
{
"default_icon": "img/icon.png",
"default_title": "我是pageAction",
"default_popup": "popup.html"
},*/
// 需要直接注入页面的JS
"content_scripts":
[
{
//"matches": ["http://*/*", "https://*/*"],
// "<all_urls>" 表示匹配所有地址
"matches": ["<all_urls>"],
// 多个JS按顺序注入
"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
// JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
"css": ["css/custom.css"],
// 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
"run_at": "document_start"
},
// 这里仅仅是为了演示content-script可以配置多个规则
{
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}
],
// 权限申请
"permissions":
[
"contextMenus", // 右键菜单
"tabs", // 标签
"notifications", // 通知
"webRequest", // web请求
"webRequestBlocking",
"storage", // 插件本地存储
"http://*/*", // 可以通过executeScript或者insertCSS访问的网站
"https://*/*" // 可以通过executeScript或者insertCSS访问的网站
],
// 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
"web_accessible_resources": ["js/inject.js"],
// 插件主页,这个很重要,不要浪费了这个免费广告位
"homepage_url": "https://www.baidu.com",
// 覆盖浏览器默认页面
"chrome_url_overrides":
{
// 覆盖浏览器默认的新标签页
"newtab": "newtab.html"
},
// Chrome40以前的插件配置页写法
"options_page": "options.html",
// Chrome40以后的插件配置页写法,如果2个都写,新版Chrome只认后面这一个
"options_ui":
{
"page": "options.html",
// 添加一些默认的样式,推荐使用
"chrome_style": true
},
// 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
"omnibox": { "keyword" : "go" },
// 默认语言
"default_locale": "zh_CN",
// devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
"devtools_page": "devtools.html"
}


### 版本差异v3
> The “background.persistent” key cannot be used with manifest_version 3. Use the “background.service_worker” key instead.


background.js不再支持ajax,使用fetch替代
background.js现支持模块化开发

这代表了我们可以使用更多的外部方法
或者减少background.js里的代码量
但只能加载本地的方法
首先需要开启权限

1
2
3
4
5
"background": { 
"service_worker": "background.bundle.js",
// "persistent": false // 不支持
"type": "module" // v3
},


### 注意事项
1. Content scripts operate within the context of the page they are injected into. chrome. isn’t available to pages.
chrome.webRequest is undefined

内容脚本在它们被注入的页面的上下文中操作。chrome.
对页面不可用。

### 能力
Chrome插件提供了很多实用API供我们使用,包括但不限于:
书签控制;
下载控制;
窗口控制;
标签控制;
网络请求控制,各类事件监听;
自定义原生菜单;
完善的通信机制;
等等;

#### override
书签管理器:从工具菜单上点击书签管理器时访问的页面,或者从地址栏直接输入 chrome://bookmarks。
历史记录:从工具菜单上点击历史记录时访问的页面,或者从地址栏直接输入 chrome://history。
新标签页:当创建新标签的时候访问的页面,或者从地址栏直接输入 chrome://newtab。
注意:一个扩展只能替代一个页面。


#### content_security_policy
content_security_policy: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy


### api

1
2
3
4
5
6
7
8
chrome.tabs
chrome.runtime
chrome.webRequest
chrome.window
chrome.storage
chrome.contextMenus
chrome.devtools
chrome.extension


chrome.runtime.getBackgroundPage(background: Window => {…}) 返回当前扩展的 background 对象
chrome.runtime.ma 返回清单文件
chrome.runtime.getURL 返回扩展中文件相对于安装位置的路径
chrome.runtime.setUninstallURL 设置卸载时要访问的 URL
chrome.runtime.reload 重新加载扩展


chrome.tabs.create(params, callback) 创建一个新的标签,以下是 params 参数
windowId 创建新标签的目标窗口,默认当前窗口
index 标签在窗口中的位置
url 标签导航的初始页面
selected 是否为选中的 默认是true
pinned 标签是否为固定
callback(tab) tab 是创建后的标签的细节,包括id
chrome.tabs.executeScript(tabId, details, callback) 向标签页注入脚本
tabId 标签页ID,默认为当前选中窗口
details.code 直接注入的脚本代码
details.file 也可以指定注入的脚本文件,与 details.code 二选一
chrome.tabs.get(tabId, callback) 获取指定标签页的细节
chrome.tabs.getSelected(windowId, callback) 获取特定窗口(windowId 默认为当前窗口) 的选中的标签
chrome.tabs.insertCSS(tabId, details, callback) 向页面注入样式
chrome.tabs.remove(tabId, callback) 移除标签


chrome.extension
> 主要被用于通信支持,提供 扩展与 content_script 之间, 扩展与扩展之间,与大多数 chrome. API 不同,chrome.extension 部分功能可以直接在 content_script 中使用
> 注意 chrome.extension 与 chrome.runtime 由很多重叠的 api , chrome.extension 比较老旧,尽量使用 chrome.runtime

chrome.extension.connect
chrome.extension.onConnect
chrome.extension.sendMessage
chrome.extension.onMessage
chrome.extension.getURL


#### chrome.cookies
在manifest.json文件中声明cookie权限以及要访问的域如下:
1
2
3
4
5
6
{
"permissions": [
"cookies",
"*://*.google.com"
],
}


chrome.cookies.CookieStore对象表示浏览器中的cookie仓库,常见的有正常模式的CookieStore和隐身模式的CookieStore。

常规方法 get | remove | set | getAll 等除外,以下是可能会用到的 api
chrome.cookies.get(object details, function(Cookie cookie) {…})
url, name, storeId
chrome.cookies.getAll(object details, function(array of Cookie cookies) {…})
chrome.cookies.set(object details, function(Cookie cookie) {…})
chrome.cookies.remove(object details, function(object details) {…})
url, name, storeId

chrome.cookies.getAllCookieStores(function(array of CookieStore cookieStores) {…})

chrome.cookies.onChanged.addListener(({removed, cookie}) => {…}) removed=true 表示的是cookie被删除的情况,否则表示被添加或者设置,cookie 表示操作的cookie




#### chrome.storage


Chrome 存储 API 提供了 2 种储存区域,分别是 sync 和 local。两种储存区域的区别在于,sync 储存的区域会根据用户当前在 Chrome 上登陆的 Google 账户自动同步数据,当无可用网络连接可用时,sync 区域对数据的读写和 local 区域对数据的读写行为一致。

对于每种储存区域,Chrome 又提供了 5 个方法,分别是 get、getBytesInUse、set、remove 和 clear。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
chrome.storage.local.set({key: value}, function() {
console.log('Value is set to ' + value);
});

chrome.storage.local.get(['key'], function(result) {
console.log('Value currently is ' + result.key);
});

chrome.storage.local.getBytesInUse(keys, function(bytes){
console.log(bytes);
});


chrome.storage.onChanged.addListener(function(changes, areaName){
console.log('Value in '+areaName+' has been changed:');
console.log(changes);
});


在 get() 方法中 keys 可以是字符串、包含多个字符串的数组或对象。
如果 keys 是对象,则会先读取以这个对象属性名为键值的数据,如果这个数据不存在则返回 keys 对象的属性值(比如 keys 为 {‘name’:’Billy’},如果 name 这个值存在,就返回 name 原有的值,如果不存在就返回 ‘Billy’)。

set() 方法为写入数据
items 为对象类型,形式为键/值对。items 的属性值如果是字符型、数字型和数组型,则储存的格式不会改变,但如果是对象型和函数型的,会被储存为 “{}”,如果是日期型和正则型的,会被储存为它们的字符串形式。

remove() 方法为删除数据,完整方法为:其中 keys 可以是字符串,也可以是包含多个字符串的数组。
clear() 方法为删除所有数据


getBytesInUse() 方法为获取一个数据或多个数据所占用的总空间,返回结果的单位是字节,完整方法为:

Chrome 同时还为存储 API 提供了一个 onChanged() 事件,当存储区的数据发生改变时,这个事件会被激发。
callback() 会接收到两个参数,第一个为 changes,第二个是 StorageArea。changes 是词典对象,键为更改的属性名称,值包含两个属性,分别为 oldValue 和 newValue;StorageArea 为 local 或 sync。







#### bookmarks
对象和属性
书签是按照树状结构组织的,每个节点都是一个书签或者一组节点(每个书签夹可包含多个节点)。每个节点都对应一个 BookmarkTreeNode 对象。
可以通过 chrome.bookmarks API来使用BookmarkTreeNode的属性。例如,当调用函数 create(),可以传入参数新节点的父节点(父节点ID),以及可选的节点索引,标题和url属性。可参看 BookmarkTreeNode 来获取节点的信息。


方法
create
get
getChildren
getRecent
getTree
move
remove
removeTree
search
update

事件
onChanged
onChildrenReordered
onCreated
onImportBegan
onImportEnded
onMoved
onRemoved


1
2
3
4
5
6
7
chrome.bookmarks.create({
'parentId': bookmarkBar.id,
'title': 'Extension bookmarks'
},
function(newFolder) {
console.log("added folder: " + newFolder.title);
});



##### BookmarkTreeNode
( object )
这个节点对象代表一个书签或者一个书签目录项。 节点对象有父子关系。
id ( string )
节点的唯一标识。IDs 在当前配置文件中是唯一的,浏览器重启后依然有效。
parentId ( optional string )
父节点的ID。根节点没有父节点。
index ( optional integer )
在父节点的书签夹范围内,该节点的索引,从0开始。
url ( optional string )
当用户点击书签时,浏览器访问的url。书签夹没有该属性 。
title ( string )
节点的说明文字。
dateAdded ( optional number )
节点创建时距纪元时间的毫秒数。 (new Date(dateAdded)).
dateGroupModified ( optional number )
书签夹内容的最后更新时间距纪元时间的毫秒数。
children ( optional array of BookmarkTreeNode )
节点的孩子的有序列表。


#### chrome.webRequest
类型
RequestFilter
HttpHeaders
BlockingResponse
UploadData
属性
MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES
方法
handlerBehaviorChanged
事件
onBeforeRequest
onBeforeSendHeaders
onSendHeaders
onHeadersReceived
onAuthRequired
onResponseStarted
onBeforeRedirect
onCompleted
onErrorOccurred



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
RequestFilter = {
tabId: interger, //optional
//URL的数组,或者是匹配URL的pattern
urls: array_of_string,
//可选的值有:"main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"
types: array_of_enumerated_string, //optional
windowId: integer //optional
};

// 设置了blocking关键字的就用这个object来作为block的规则了
BolockingResponse = {
//为true的话request被cancel,在onBeforeRequest里面用哦
cancel: boolean, //optional
//只在onBeforeRequest事件中使用,用来掉包的关键属性!!!
redirectUrl: string, //option
//只用在onHeadersReceived事件里,在浏览器返给server时把header给掉包
responseHeaders: HttpHeaders //optional
//只在onBeforeSendHeaders事件中使用。是另一个用来掉包的关键属性!!!
requestHeaders: HttpHeaders //optional
//只在onAuthRequred事件中使用,当然也是用来掉包的
authCredentials: object //optional
};

// HttpHeaders:HTTP headers组成的数组,数组每个元素都有自己的键值对,就是object了。这个得实际打印出来才知道。

/**
* 传给callback的参数details结构如下
*/
details = {
tabId: integer, //如果没有和tab关联则返回-1
parentFrameId: integer,
url: string,
timeStamp: double,
//0表示request是在main frame里发生的
frameId: integer,
requestId: string,
requestHeaders: HttpHeaders, // optional
type: enumerated_string, //value in: ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"]
method: string //标准HTTP方法
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// a,阻止所有发往www.evile.com的request
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
return {cancel: details.url.indexOf("://www.evil.com/") != -1};
},
{urls: ["<all_urls>"]},
["blocking"]);

// 另一种方法,使用filter:符合filter的都被cancel掉了。
chrome.webRequest.onBeforeRequest.addListener(
function(details) { return {cancel: true}; },
{urls: ["*://www.evil.com/*"]},
["blocking"])


// b,从所有的request中删除User-Agent的header
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
for (var i = 0; i < details.requestHeaders.length; ++i) {
if (details.requestHeaders[i].name === 'User-Agent') {
details.requestHeaders.splice(i, 1);
break;
}
}
return {requestHeaders: details.requestHeaders};
},
{urls: ["<all_urls>"]},
["blocking", "requestHeaders"]);






## 跨浏览器


为了让开发者不需要写多套代码,Mozilla 发布了 WebExtensions API。WebExtensions API 主要基于 JavaScript、HTML 和 CSS,可以重新打包并在 Chrome、Firefox 和 Edge 等其他浏览器中使用。

6月27日消息,在WWDC2020,苹果宣布
近日,苹果宣布 Safari 也可以使用 WebExtensions API 了。苹果此前所使用的 Safari App Extensions 虽然在 macOS 应用程序和 Safari 之间共享代码很便捷,但无法将组件移植到其他浏览器,也无法将其他浏览器的组件移植到 Safari 中。

值得注意的是,Safari Web 扩展需要在 macOS 11 及更高版本或者安装了 Safari 14 的 macOS 10.14.6 或 10.15.6 中使用。



## chrome
位置:
Mac: ~/Library/Application Support/Google/Chrome/Default/Extensions/fmkxxxxxxxxxnihi/4.10.1_0

### 安装



## firefox

1、提前准备好Chrome插件。
3、打开Firefox开发者网址
https://addons.mozilla.org/zh-CN/firefox/
6、登录后,点击页面右上角“开发者中心”
7、点击右下角“提交新附加组件”
8、选择如何分发此版本。
因为我们上传的是别人开发的东西,只是用此方法来转换,所以一定请选“我自己托管”。不这样选,插件会出现在插件库中,全网可以搜索到。当然,大概率是你上传后根本审核不通过……
9、点击“选择文件”,选择刚才你准备好的插件文件。
10、选定你准备好的插件后,插件会自动上传并进行验证,
11、出现让你提交源代码的页面
13、找到你刚才提交的附加组件并点击


## safari
### 文档
[Meet Safari Web Extensions]


### 转化现有插件

xcrun safari-web-extension-converter
xcrun safari-web-extension-converter /big/ass/path/to/chrome/extensions/from/above/{EXTENSION ID}/{some version number}

1. First, you need to be on Big Sur with the latest Xcode (12) installed. But! There’s a big asterisk to this which I’ll get to later.

I’ve successfully converted extensions like Refind, Notion, DF Tube, the 33Msil extension, and a couple of others. From what I can tell extensions that change the page layout somehow or grab URLs work.


Big asterisk: From the limited testing I’ve done you can’t convert extensions on Catalina with Xcode 12 installed BUT you can build them. What I’ve done is converted the extensions on my macOS 11 machine and then copied them over to my Catalina machine and built there. These extensions will run in the Safari 14 beta for Catalina.



#### 问题
1. scrub: error: unable to find utility "safari-web-extension-converter", not a developer tool or in PATH.
> I originally tried this on Xcode 12.5 Beta.

Try launching the terminal and reselecting your Xcode install as the primary. Type:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
or if you’re running a beta version,
sudo xcode-select -s /Applications/Xcode-beta.app/Contents/Developer







# 浏览器插件问题
## chrome
1. chrome 中可以设置和修改插件的 快捷键
chrome://extensions/shortcuts


## safari
### 安装
#### 官方

您需要 Safari 浏览器 12 或更高版本才能从 App Store 获取 Safari 浏览器扩展。

如上图所示,App Store 将打开并显示“Safari 浏览器扩展”页面。要下载并安装扩展,请点按“获取”或点按价格。系统可能会要求您使用 Apple ID 登录。




#### 自己安装

Safari浏览器 -》偏好设置 -》 菜单栏显示开发选项
菜单栏 -》 开发选项 -》 显示拓展构建器 -》 添加拓展






> safari浏览器不再支持不安全的拓展,你可以在app store或Safari浏览器拓展库中查找经过apple审核的更新拓展。
将下载好的 tampermonkey.safariextz 改为 tampermonkey.zip。

解压这个zip文件,我喜欢用 The Unarchiver解压,官方的默认解压工具容易陷入.cpgz与.zip的死循环之中。


下载的源压缩文件:tampermonkey.safariextz
解压后文件:tampermonkey.safariextension


##### 其他评论:
求助:safari重启后tampermonkey和下载的插件就都不见了有解决方法吗?
你好 我在chrome浏览器安装成功 但电脑关机后油猴扩展就消失需要重新安装 请问有解决方案吗

safari13貌似没有扩展架构器了,你看看
这本来是给开发者开发完插件调试使用的,最新的Safari 13 是官方移除了这个选项,暂时无解。



### 使用

二、Safari浏览器启用safari插件
Safari 可能还会询问您是否想要信任该网站以使用该插件:
四、如何允许或阻止网站使用safari插件


以安全模式运行:如果您允许该插件以安全模式运行,Safari 会允许该网站使用该插件,并运行 Mac 内置的恶意软件安全检查。如果以不安全模式运行,Safari 会允许该网站使用该插件,但是不会运行恶意软件安全检查。这存在安全风险,因此除非您信任该网站,并且没有其他方式来查看内容,否则请勿停用安全模式。



### 插件



# 开发
## 基础
### api
1. declarative_net_request权限rules动作为requestHeaders时不能设置append操作。
1. The ‘webRequest’ API cannot be used with event pages.
> For those who don’t want to use the new Event Pages and would prefer to stick with Background Pages, make sure to set “persistent”: true in your manifest file’s background property.
对于那些不想使用新的事件页面并且更喜欢坚持使用背景页面的人,请确保在清单文件的background属性中设置”persistent”: true。


### v3注意
Migrating to Manifest V3: https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/

0. permission 分成了3块
1. background scripts 改成server_worker
2. page action 和browser action 统一为action
3. Chrome Extensions: You do not have permission to use blocking webRequest listeners
v3 已经不建议webRequestBlocking, 即使添加了权限。新的能力Declarative Net Request
> The blocking version of the Web Request API exists in Manifest V3, but it can only be used by extensions that are force-installed using Chrome’s enterprise policies: ExtensionSettings, ExtensionInstallForcelist.

4. Web-accessible resources

<!–
## 自动加载
https://github.com/rubenspgcavalcante/webpack-extension-reloader
https://github.com/v-limc/crx-auto-reload-plugin
https://github.com/cezaraugusto/webpack-run-chrome-extension

无脑选webpack-extension-reloader。 星多,且bkmv的模版也在用。虽然提交记录久了。

github数据对比。From 20220731
name | form | star | issue(open/23) | last commit | others
– | – | – | – | – | –
webpack-extension-reloader | 69 | 453 | 46/23 | 22 Nov 2019 | bkmv的模版也在用
crx-auto-reload-plugin | 0 | 3 | 0/0 | 30 Jan 2021 | 有点中文
webpack-run-chrome-extension | 2 | 20 | 4/2 | 12 Feb 2022 | 0配置
### Webpack Extension Reloader
Note: This plugin doesn’t allow Hot Module Replacement (HMR) yet.
What it does?
Basically something similar to what the webpack hot reload middleware does. When you change the code and the webpack trigger and finish the compilation, your extension is notified and then reloaded using the standard browser runtime API.
Check out Hot reloading extensions using Webpack for more background.



### crx-auto-reload-plugin
How it works
server side with ‘webpack –watch’ mode
Parse and modify the ‘manifest.json’ asset if exists to inject auto reload script.
* Always generate a ‘auto-reload.js’ asset in every build, for watching changes.

client side
Watch for ‘auto-reload.json’ file’s ‘lastModified’ change and auto call chrome.runtime.reload().


### webpack-run-chrome-extension workflow npm
Run your browser extension on Chrome with zero-config auto-reload support
Opens up a new Chrome instance with an extension loaded. Resources declared in manifest.json are auto-reloaded by default, including JavaScript and CSS declared in Manifest HTML pages. This plugin accepts all flags Chrome does (see browserFlags) and loads on a clean profile by default. –>

## 开发模版
###
chrome-extension-boilerplate-react: https://github.com/lxieyang/chrome-extension-boilerplate-react.git
> This is a basic Chrome Extensions boilerplate to help you write modular and modern Javascript code, load CSS easily and automatic reload the browser on code changes.

Chrome Extension Boilerplate with React 17 and Webpack 5
This boilerplate adopts Manifest V3!
个人小结:试用一下。

###
react-chrome-extension-boilerplate:https://github.com/jhen0409/react-chrome-extension-boilerplate
> Boilerplate for Chrome Extension React.js project.
最新更新:3 Feb 2018;Start:2.1k
个人小结:
有些落后。webpack3,react15。
模版有些重,包括了redux。
目录结构未找到pages,对齐chrome插件结构明确。

###
plasmo: https://github.com/PlasmoHQ/plasmo
> The Plasmo Framework is a battery-packed browser extension SDK made by hackers for hackers. Build your product and stop worrying about config files and the odd peculiarities of building browser extensions.
个人小结:
虽然有文档,但是提高了门槛。
不是一个单纯的模版。有些复杂。目录不名,manifest文件不清。
react模版。
2022.7有更新。Start:3.9k



###
extension-boilerplate:https://github.com/EmailThis/extension-boilerplate
> A foundation for creating browser extensions for Chrome, Opera & Firefox.
PS: 使用gulpfile.babel.js。未使用前端框架。
最新更新:8 Jun 2017。Start:3.1k



knowledge is no pay,reward is kindness
0%